#include "constantes.h"
#include "genetico.h"
#include "colocacion.h"
#include "union.h"

using namespace std;


/********************************/
/******		Cabeceras	   ******/
void calculaDatosCajas(multimap<string,pair<string,Box> >::iterator , multimap<string,pair<string,Box> >::iterator , int &,double &,double &,int );
double calculaPercentilPeso(multimap<string,pair<string,Box> >::iterator , multimap<string,pair<string,Box> >::iterator, double);
double calculaPercentilVolumen(multimap<string,pair<string,Box> >::iterator , multimap<string,pair<string,Box> >::iterator, double);
double calculaPercentilSuperficie(multimap<string,pair<string,Box> >::iterator , multimap<string,pair<string,Box> >::iterator, double);
bool rankingOrder(Solucion,Solucion);
void rebuildSequence(list<Box> *cajas);

Type getMaxType()
{	
	int max=0;
	Type t;
	for(int i=0; i<TIPOS.size(); ++i)
		if(TIPOS[i].getMax()>max)
		{
			t=TIPOS[i];
			max = TIPOS[i].getMax();
		}
	return t;
}

bool ordenaCajas(Box b1, Box b2)
{
	return b1.getWidth()*b1.getDepth()>b2.getWidth()*b2.getDepth();
}

//###############################################################
//
//							MAIN
//
//###############################################################
int main(int argc, char* argv[])
{
	switch(argc)
	{
	case	1:
	case	3:
		break;

	case	2:
	case	4:
		CONFIG_FILE = argv[1];
		break;

	default:
		cerr<<"\nUso: containerfilling.exe [configfile] [inputfile outputfile]"<<endl;
		exit(-1);
	}

	//Cargar los datos de configuración desde el XML
	if(parsearConfiguracion(CONFIG_FILE.c_str(),INPUT_FILE,OUTPUT_FILE, WIDTH,HEIGHT,DEPTH,WEIGHT,
		GEN_MAX,INIT_POP_SIZE,TYPE_GEN,CROSS_PROB,MUTAT_PROB,CROSS_OPERATOR,MUTAT_OPERATOR,FIT_OPERATOR,
		SELECT_OPERATOR, TOURN_PROB, POOL_SIZE,REPROD_OPERATOR,REPLACE_OPERATOR,CONVERGENCIA,W_LIMIT,
		CROSS_WEIGHT,W_WEIGHT,PERC_W, PERC_V, PERC_A, TIPOS))
	{
		cerr<<"\nError cargado la configuración desde "<< CONFIG_FILE <<endl;
		exit(1);
	}

	switch(argc)
	{
		case 3:
			INPUT_FILE = argv[1];
			OUTPUT_FILE =argv[2];
			break;

		case	4:
			INPUT_FILE = argv[2];
			OUTPUT_FILE =argv[3];
			break;
	}

	//Cargar los datos desde el XML
	list<Box> datos_iniciales;
	list<Box>::iterator it_di;
	if(parsearEntradaCajas(datos_iniciales,INPUT_FILE))
	{
		cerr<<"\nError cargado los datos desde "<< INPUT_FILE <<endl;
		exit(1);
	}

	//Iniciar la semilla de aleatorios
	srand((unsigned int)time(NULL));	
	//srand(0);

	time_t tiempo_inicial_ga,
		   tiempo_inicial_cc,
		   tiempo_final_ga,
		   tiempo_final_cc,
		   tiempo_inicial,
		   tiempo_final;
	
	time(&tiempo_inicial);
	//Map de destinos: 
	//	key:destino
	//	elemento: <cliente,caja>
	multimap<string,pair<string,Box> > DESTINOS;
	multimap<string,pair<string,Box> >::iterator it_des,
												 it_des2;


	//IMPRIMIR EN LA SALIDA EL TIPO DE CONFIGURACION
		string cross, 
		pobini, 
		mut, 
		select, 
		reprod, 
		replace;

	switch(TYPE_GEN)
	{	
	case PALEAT: pobini="ALEAT"; break;
		case PPARES: pobini="PARES";break;
		case PTRIOS: pobini="TRIOS";break;
		case PBLOQUES: pobini="BLOQUES";break;
		case PMIX: pobini="MIX";break;
	
	}
	switch(CROSS_OPERATOR)
	{
		case OP: cross="OP";break;
		case OX: cross ="OX";	break;//Linear Order
		case PMX: cross = "PMX";break;	//Partially Matched
		case CX: cross = "CX";break;	//Cycle
		case PBX: cross = "PBX";break;	//Position Based
		case OBX: cross = "OBX";break;	//Ordered Based
	}
	switch(MUTAT_OPERATOR)
	{
		case OM: mut = "OM";break;
		case NM: mut = "NM";break;
		case PM: mut = "PM";break;
		case IM: mut = "IM";break;
	}

	switch(SELECT_OPERATOR)
	{
		case SRANK: select="SRANK";break;
		case STOURN: select="STOURN";break;
	}
	switch(REPROD_OPERATOR)
	{
		case RR: reprod = "RR";break;
		case OR: reprod = "OR";break;
	}
	switch(REPLACE_OPERATOR)
	{
		case RRANK: replace = "RRANK";break;
		case RTOURN: replace = "RTOURN";break;
	}

	cerr<<endl;
	cerr<<"Pob_Ini\tCross\tCross_prob\tMut\tMut_prob\tSelect\tReprod\tReplace"<<endl;
	cerr<<pobini<<"\t"<<cross<<"\t"<<CROSS_PROB<<"\t"<<mut<<"\t"<<MUTAT_PROB<<"\t"<<
		select<<"\t"<<reprod<<"\t"<<replace<<endl;


	//Map de secuencias solucion
	//	key:destino
	//	elemento: secuencia
	//multimap<string, secuencia > SECUENCIAS;

	//Map de contenedores solucion
	//key: destino
	//elemento: contenedor
	multimap<string, Container> CONTENEDORES;
	int num_contenedores=0;	//numero total de contenedores de la solucion

	vector<Box> LIST_A,	//Cajas que ocupan el suelo de los contenedores
				LIST_B;	//Cajas que ocupan, a priori, el 2º nivel del contenedor
			  
	Solucion solucion;
	datos_genetico salida;

	set<string> destinos;	//Destinos posibles
	set<string>::iterator it_sd;
	pair<multimap<string,pair<string,Box> >::iterator, multimap<string,pair<string,Box> >::iterator> ret;

	//Datos necesarios para imprimir resultados
	multimap<string, Container>::iterator it_cont;
	list<Container>::iterator it_ord;
	double pbz1, pbz2, pbx1, pbx2, difz, difx;
	
	//Rellenar el map de destinos con las cajas cargadas del fichero
	for(it_di=datos_iniciales.begin();it_di!=datos_iniciales.end(); it_di++)
	{
		DESTINOS.insert(pair<string,pair<string,Box> >(
			(*it_di).getDestination(),pair<string,Box>((*it_di).getClient(),(*it_di)))
			);
		destinos.insert((*it_di).getDestination());
	}	

	//para cada destino, separar la ejecución del algoritmo por clientes
	string cliente;
	for(it_sd=destinos.begin(); it_sd!=destinos.end(); it_sd++)
	{
		ret=DESTINOS.equal_range(*it_sd);
		it_des=ret.first;
		while(it_des!=ret.second)	//*it_di recorre cajas para cada cliente
		{
			//Compruebo si existe algun error en los identificadores de las cajas
			for(it_des2=++it_des, it_des--; it_des2!=ret.second; it_des2++)
			{
				if((*it_des).second.second.getId() == (*it_des2).second.second.getId())
				{
					cerr<<"Hay 2 o mas cajas diferentes con el mismo identificador"<<endl;
					exit(1);
				}
			}
			//Calculo los percentiles para el destino
			pc_pes = calculaPercentilPeso(ret.first, ret.second, PERC_W);
			pc_vol = calculaPercentilVolumen(ret.first, ret.second, PERC_V);
			pc_sup = calculaPercentilSuperficie(ret.first,ret.second,PERC_A);
			
			//Recorro por clientes
			cliente=(*it_des).second.first;	//Cliente actual
			while(it_des!=ret.second && cliente==(*it_des).second.first)
			{
				cliente=(*it_des).second.first;

				//Si el contenedor tiene mas ancho que largo y la caja es mas larga que ancha roto 90º
				//Lo mismo si el contenedor es mas largo que ancho.
				if(((*it_des).second.second.getWidth() > WIDTH && (*it_des).second.second.getDepth() <= WIDTH && (*it_des).second.second.getWidth() <= DEPTH)
					|| ((*it_des).second.second.getDepth() > DEPTH && (*it_des).second.second.getWidth() <= DEPTH) && (*it_des).second.second.getDepth() <=WIDTH)
				{
					int n_width= (*it_des).second.second.getDepth();
					int n_depth = (*it_des).second.second.getWidth();
					(*it_des).second.second.setWidth(n_width);
					(*it_des).second.second.setDepth(n_depth);			
				}
				//Una vez hechos los cambios, si no cabe en el contenedor, se devuelve un error y sigue con la siguiente caja
				if((*it_des).second.second.getWidth() > WIDTH || (*it_des).second.second.getDepth() > DEPTH || (*it_des).second.second.getHeight() > HEIGHT)
				{
					cerr<<"\nError la caja con ID: "<<(*it_des).second.second.getName()<<" no cabe en el contenedor."<< INPUT_FILE <<endl;
					continue;
				}

				//Si la caja es de tipo M o H y se encuentra con valores de peso, vol o sup menores a los percentiles, a la lista B
				if(	 (*it_des).second.second.getType()!=getMaxType()
					&&	((*it_des).second.second.getWeight() < pc_pes 
						||	(*it_des).second.second.getVolume() < pc_vol
						||	(*it_des).second.second.getWidth()*(*it_des).second.second.getDepth() < pc_sup)
						)
				{
					LIST_B.push_back((*it_des).second.second);
				}
				//Si la caja es de tipo B, o es M o H mayor que los percentiles, a la lista A		
				else
					LIST_A.push_back((*it_des).second.second);
				
				it_des++;
			} //while

			//IMPRIMO LAS LISTAS A Y B PARA VER COMO HAN QUEDADO
			/*vector<Box>::iterator it;
			cerr<<"CAJAS EN LA LISTA A: "<<endl;
			for(it=LIST_A.begin(); it!=LIST_A.end(); it++)
				cerr<<(*it)<<endl;
			cerr<<"CAJAS EN LA LISTA B: "<<endl;
			for(it=LIST_B.begin(); it!=LIST_B.end(); it++)
				cerr<<(*it)<<endl;*/
			//cerr<<"\nCAJAS EN LA LISTA A: \t"<<LIST_A.size()<<endl;
			//cerr<<"\nCAJAS EN LA LISTA B: \t"<<LIST_B.size()<<endl;
			
			

			//Ejecutar el Algoritmo Genético para el cliente actual
			//cerr<<"\n\n#############################################################";
			if(LIST_A.size()>0)
			{
				//cerr<<"\nComienza la ejecucion del GA para el destino "<< *it_sd <<" cliente "<< cliente <<endl;
				time(&tiempo_inicial_ga);
				solucion=algoritmoGenetico(LIST_A, salida);
				time(&tiempo_final_ga);
			}
			else
			{
				//cerr<<"\nNo se lanza el GA."<<endl;
			}
			string pobl;
			switch(solucion.poblacion)
				{	
				case AL: pobl="ALEATORIO"; break;
					case P: pobl="PARES";break;
					case TR: pobl="TRIOS";break;
					case BL: pobl="BLOQUES";break;
					
				}
			cerr<<endl;
			cerr<<"Fitness_Ini\t"<<"Fitness_Fin\t"<<"Tipo_Pob\t"<<"Generaciones\t"<<endl;
			cerr<<salida.f_ini<<"\t"<<salida.f_fin<<"\t"<<pobl<<"\t"<<salida.gen<<"\t"<<endl;
			
			cerr<<endl;
			cerr<<"% Llenado\t"<<"% Var_Z\t"<<"% Var_X\t"<<endl;
			for(it_ord=solucion.orden.begin(); it_ord!=solucion.orden.end(); it_ord++)
			{ 
				pbz1 = (it_ord)->getPesoBloqueZ(0);
				pbz2 = (it_ord)->getPesoBloqueZ(1);
				difz = ABSOLUTE(pbz1-pbz2);
				difz = difz/(pbz1+pbz2)*100;

				pbx1 = (it_ord)->getPesoBloqueX(0);
				pbx2 = (it_ord)->getPesoBloqueX(1);
				difx = ABSOLUTE(pbx1-pbx2);
				difx =  difx/(pbx1+pbx2)*100;

				cerr<<it_ord->getOcupacion()<<"\t"<<difz<<"\t"<<difx<<endl;
			}
			
			sort(LIST_B.begin(), LIST_B.end(),ordenaCajas);

			//Cargar la lista B en la solucion
			solucion.list_B.resize(LIST_B.size());
			for(int i=0; i<LIST_B.size(); i++)
				solucion.list_B[i]=LIST_B[i];

			

			//Ejecutar la colocación de las cajas
			//cerr<<"\nComienza la ejecucion de CC."<<endl;
			time(&tiempo_inicial_cc);
			
			colocaContenedores(CONTENEDORES,solucion, (*it_sd), cliente, num_contenedores);

			time(&tiempo_final_cc);

			//if(LIST_A.size()>0)
			//	cerr<<"\nTiempo GA: "<< tiempo_final_ga-tiempo_inicial_ga;
			//cerr<<"\nTiempo CC: "<< tiempo_final_cc-tiempo_inicial_cc<<endl;

			//Resetear listas de cajas
			LIST_A.clear();
			LIST_B.clear();

		//} //while it_des

			time(&tiempo_final);

		} //while it_des


		//Imprimir las cajas en el fichero POR DESTINOS
		int n_cont=1;
		multimap<string,Container>::iterator it_con;
		pair<multimap<string,Container>::iterator, multimap<string,Container>::iterator> ret2;
		ret2 = CONTENEDORES.equal_range((*it_sd));
		it_con=ret2.first; 
		//Imprime la cabecera en el fichero
		imprimeCabecera(DESTINOS.count((*it_sd)), CONTENEDORES.count((*it_sd)), OUTPUT_FILE+(*it_con).first+".das");
		//Imprime las cajas
		for(it_con; it_con!=ret2.second; it_con++)
		{
			//rebuildSequence((*it_con).second.getBoxes());
			imprimeCajas((*it_con).second.getBoxes(),  OUTPUT_FILE+(*it_con).first+".das", n_cont);
			n_cont++;
		}

		escribirResultadosXML(OUTPUT_FILE,(*it_sd),CONTENEDORES);
	} //for it_sd
	//cerr<<"\n\n#############################################################";
	
	/**
	*	Union de contenedores poco rellenos
	*/
	mixContainer(CONTENEDORES, DESTINOS); //Si se unieron contenedores, destino guardara el destino de los contenedores 


	time(&tiempo_final);
	//cerr<<"\nTiempo TOTAL: "<< tiempo_final-tiempo_inicial<<endl;
		

	cerr<<endl;
	cerr<<"RESULTADO FINAL"<<endl;
	cerr<<"Tpo_Total"<<endl;
	cerr<<tiempo_final-tiempo_inicial<<endl;
	
	cerr<<endl;
	cerr<<"% Llenados\t"<<"% Var_Z\t"<<"% Var_X\t"<<endl;
	for(it_cont=CONTENEDORES.begin(); it_cont!=CONTENEDORES.end(); it_cont++)
	{
		pbz1 = (*it_cont).second.getPesoBloqueZ(0);
		pbz2 = (*it_cont).second.getPesoBloqueZ(1);
		difz = ABSOLUTE(pbz1-pbz2);
		difz = difz/(pbz1+pbz2)*100;

		pbx1 = (*it_cont).second.getPesoBloqueX(0);
		pbx2 = (*it_cont).second.getPesoBloqueX(1);
		difx = ABSOLUTE(pbx1-pbx2);
		difx =  difx/(pbx1+pbx2)*100;

		cerr<<it_cont->second.getOcupacion()<<"\t"<<difz<<"\t"<<difx<<endl;
	}
	exit(0);
}



//###############################################################
//
//					SEPARACION DE LISTAS
//
//###############################################################
/**
*	Calcula los datos de las cajas:
*	Si id == 0: en H, M y B devuelve los PESOS TOTALES de las cajas de tipo H, M y B
*	Si id == 1: en H, M y B se devuelven los VOLUMENES de las cajas de tipo H, M y B
*	Si id == 2: en H, M y B se devuelven las SUPERFICIES de las cajas de tipo H, M y B
*	En n_cajas devuelve el número de cajas total 
*	En max el peso, volumen o superficie máximo de las cajas B
*	En min el peso, volumen o superficie minimo de las cajas H
*/
void calculaDatosCajas(multimap<string,pair<string,Box> >::iterator beg, 
					 multimap<string,pair<string,Box> >::iterator end, 
					 int &n_cajas,
					 double &max,
					 double &min,
					 int id)
{

	max = 0.0;
	min = numeric_limits<double>::max( );
	n_cajas=0;
	Box aux;

	if(id==0)
	{
		for(beg; beg!=end; beg++)
		{	aux = (*beg).second.second;
			n_cajas++;

			if(aux.getWeight() > max)
				max=aux.getWeight();

			if(aux.getWeight() < min)
				min=aux.getWeight();
			
		}
	}
	else 
		if(id==1)
		{
			for(beg; beg!=end; beg++)
			{	aux = (*beg).second.second;
				n_cajas++;
				if(aux.getVolume() > max)
					max=aux.getVolume();
						
				if(aux.getVolume() < min)
					min=aux.getVolume();
						
			}
		}
		else
		{
			for(beg; beg!=end; beg++)
			{	aux = (*beg).second.second;
				n_cajas++;

				if(aux.getWidth()*aux.getDepth() > max)
					max=aux.getWidth()*aux.getDepth();
				if(aux.getWidth()*aux.getDepth() < min)
					min=aux.getWidth()*aux.getDepth();	
			}
		}

}
/**
*	Devuelve el peso limite que define si una caja esta en la lista A o en la lista B
*	k es el percentil que queremos calcular
*/
double calculaPercentilPeso(multimap<string,pair<string,Box> >::iterator beg, 
					 multimap<string,pair<string,Box> >::iterator end, double k)
{
	int n_cajas;
	double pMax, pMin;
	double percentil_k;

	calculaDatosCajas(beg,end,n_cajas,pMax,pMin,0);
	
	//Calculamos el pecentil k sobre los datos que tenemos
	percentil_k= ( ( (k*((double)n_cajas/100)) )/(n_cajas) )*( pMax-pMin );

	return percentil_k;
}

/**
*	Devuelve el peso limite que define si una caja esta en la lista A o en la lista B
*	k es el percentil que queremos calcular
*/

double calculaPercentilVolumen(multimap<string,pair<string,Box> >::iterator beg, 
					 multimap<string,pair<string,Box> >::iterator end, double k)
{
	int n_cajas;
	double pMax, pMin;
	double percentil_k;

	calculaDatosCajas(beg,end,n_cajas,pMax,pMin,1);
	
	//Calculamos el pecentil k sobre los datos que tenemos
	percentil_k= ( ( (k*((double)n_cajas/100)) )/(n_cajas) )*( pMax-pMin );

	return percentil_k;

}

/**
*	Devuelve el peso limite que define si una caja esta en la lista A o en la lista B
*	k es el percentil que queremos calcular
*/
double calculaPercentilSuperficie(multimap<string,pair<string,Box> >::iterator beg, 
					 multimap<string,pair<string,Box> >::iterator end, double k)
{
	int n_cajas;
	double pMax, pMin;
	double percentil_k;

	calculaDatosCajas(beg,end,n_cajas,pMax,pMin,2);
	
	//Calculamos el pecentil k sobre los datos que tenemos
	percentil_k= ( ( (k*((double)n_cajas/100)) )/(n_cajas) )*( pMax-pMin );

	return percentil_k;
}
/*
*	Busca dentro de 'esquinas' las que hayan podido ser ocupadas parcialmente
*	por la inserción de 'caja'.
*	Las modifica adecuando sus dimensiones a la nueva colocación.
*
*/


/*
*	Ordena el vector de cajas de acuerdo a la inserción física
*	que se lleva a cabo en el contenedor.
*/

bool compBoxes(Box &a, Box &b)
{
	if(a.getZ()< b.getZ())
		return true;
	else if(a.getZ()== b.getZ())
	{
		if(a.getX() < b.getX())
			return true;
		else if (a.getX() == b.getX())
		{
			if(a.getY() <= b.getY())
				return true;
			else
				return false;
		}
		else
			return false;
	}
	else
		return false;
}
void rebuildSequence(list<Box> *cajas)
{
	cajas->sort(compBoxes);
}

